# gozerbot/fleet.py
#
#

""" fleet is a list of bots. """

__copyright__ = 'this file is in the public domain'

# ==============
# IMPORT SECTION

# gozerbot imports
from gozerbot.datadir import datadir
from utils.exception import handle_exception
from utils.generic import waitforqueue
from utils.log import rlog
from utils.locking import lockdec
from config import Config, fleetbotconfigtxt, config
from users import users
from plugins import plugins
from simplejson import load

# basic imports
import Queue, types, time, pickle, glob, logging, shutil

# END IMPORT

# ============
# LOCK SECTION


# END LOCK
# ========

## START

class FleetBotAlreadyExists(Exception):
    pass


class Fleet(object):

    """
        a fleet contains multiple bots (list of bots). used the datadir
        set in gozerbot/datadir.py

    """

    def __init__(self):
        self.datadir = datadir + '/' + 'fleet'
        self.bots = []

    def getfirstbot(self):

        """
            return the main bot of the fleet.

            :rtype: gozerbot.botbase.BotBase

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.getfirstbot

        """
        self.startok.wait()
        return self.bots[0]
        
    def size(self):

        """
             return number of bots in fleet.

             :rtype: integer

             .. literalinclude:: ../../gozerbot/fleet.py
                 :pyobject: Fleet.size

        """

        return len(self.bots)

    def makebot(self, name, cfg=None):

        """
            create a bot .. use configuration if provided.

            :param name: the name of the bot
            :type name: string
            :param cfg: configuration file for the bot
            :type cfg: gozerbot.config.Config

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.makebot

        """

        if self.byname(name):
            raise FleetBotAlreadyExists("there is already a %s bot in the fleet" % name)

        rlog(10, 'fleet', 'making bot')
        bot = None

        # if not config create a default bot
        if not cfg:
            cfg = Config(self.data + '/' + name + '/' + 'config', inittxt=fleetbotconfigtxt)
            cfg.save()

        # create bot based on type 
        if cfg['type'] == 'irc':
            from gozerbot.irc.bot import Bot
            bot = Bot(name, cfg)
        elif cfg['type'] == 'xmpp' or cfg['type'] == 'jabber':
            from gozerbot.xmpp.bot import Bot
            bot = Bot(name, cfg)
        else:
            rlog(10, 'fleet', '%s .. unproper type: %s' % (cfg['name'], cfg['type']))

        # set bot name and initialize bot
        if bot:
            cfg['name'] = bot.name = name
            self.initbot(bot)
            return bot

        # failed to created the bot
        raise Exception("can't make %s bot" % name)

    def start(self, botlist=[], enable=False):

        """
            startup the bots.

            :param botlist: list of bots to start .. if not provided the bots in the gozerdata/fleet dir will be used
            :type botlist: list
            :param enable: whether the bot should be enabled 
            :type enable: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.start

        """

        # scan the fleet datadir for bots
        dirs = []
        got = []
        for bot in botlist:
            dirs.append(self.datadir + '.' + bot)

        if not dirs:
            dirs = glob.glob(self.datadir + '/' + "*")

        for fleetdir in dirs:

            if fleetdir.endswith('fleet'):
                continue

            rlog(10, 'fleet', 'found bot: ' + fleetdir)
            cfg = Config(fleetdir, 'config')

            if not cfg:
                rlog(10, 'fleet', "can't read %s config file" % fleetdir)
                continue

            name = fleetdir.split('.')[-1]

            if not name:
                rlog(10, 'fleet', "can't read botname from %s config file" % \
fleetdir)
                continue

            if not enable and not cfg['enable']:
                rlog(10, 'fleet', '%s bot is disabled' % name)
                continue
            else:
                rlog(10, 'fleet', '%s bot is enabled' % name)

            if not name in fleetdir:
                rlog(10, 'fleet', 'bot name in config file doesnt match dir name')
                continue

            try:
                bot = self.makebot(name, cfg)
            except FleetBotAlreadyExists:
                rlog(10, 'fleet', 'there is already a fleetbot with the name %s' % name)
                continue

            if bot:
                self.addbot(bot)
                got.append(bot)

        # set startok event
        self.startok.set()

        return got

    def save(self):

        """
            save fleet data and call save on all the bots.

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.save

        """

        for i in self.bots:

            try:
                i.save()
            except Exception, ex:
                handle_exception()

    def avail(self):

        """
            show available fleet bots.

            :rtype: list

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.avail

        """

        return os.listdir(self.datadir)

    def list(self):

        """
            return list of bot names.

            :rtype: list

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.list

        """

        result = []

        for i in self.bots:
            result.append(i.name)

        return result

    def stopall(self):

        """ 
            call stop() on all fleet bots.

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.stopall

        """

        for i in self.bots:

            try:
                i.stop()
            except:
                pass

    def byname(self, name):

        """
            return bot by name.

            :param name: name of the bot
            :type name: string

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.byname

        """

        name = name.lower()

        for i in self.bots:
            if name == i.name:
                return i

    def replace(self, name, bot):

        """
            replace bot with a new bot.

            :param name: name of the bot to replace
            :type name: string
            :param bot: bot to replace old bot with
            :type bot: gozerbot.botbase.BotBase

            .. literalinclude:: ../../gozerbot/fleet.py
                 :pyobject: Fleet.replace

        """

        name = name.lower()
        for i in range(len(self.bots)):
            if name == self.bots[i].name:
                self.bots[i] = bot
                return

    def initbot(self, bot):

        """
            initialise a bot.

            :param bot: bot to initialise
            :type bot: gozerbot.botbase.BotBase

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.initbot

        """

        if bot not in self.bots:

            if type(bot.cfg['owner']) == types.StringType or type(bot.cfg['owner']) == types.UnicodeType:
                bot.cfg['owner'] = [bot.cfg['owner'], ]
                bot.cfg.save()

            users.make_owner(config['owner'] + bot.cfg['owner'])
            rlog(10, 'fleet', 'added bot: ' + bot.name)

    def addbot(self, bot):

        """
            add a bot to the fleet .. remove all existing bots with the 
            same name.

            :param bot: bot to add
            :type bot: gozerbot.botbase.BotBase
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.addbot
            
        """

        if bot:

            for i in range(len(self.bots)-1, -1, -1):
                if self.bots[i].name == bot.name:
                    rlog(10, 'fleet', 'removing %s from fleet' % bot.name)
                    del self.bots[i]

            rlog(10, 'fleet', 'adding %s' % bot.name)
            self.bots.append(bot)
            return True

        return False

    def connect(self, name):

        """
            connect bot to the server.

            :param name: name of the bot
            :type name: string
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.connect

        """

        name = name.lower()

        for i in self.bots:

            if i.name == name:
                got = i.connect()

                if got:
                    i.joinchannels()
                    return True
                else:
                    return False

    def delete(self, name):

        """
            delete bot with name from fleet.

            :param name: name of bot to delete
            :type name: string
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.delete

        """

        for i in self.bots:

            if i.name == name:
                i.exit()
                self.remove(i)
                i.cfg['enable'] = 0
                i.cfg.save()
                rlog(10, 'fleet', '%s disabled' % i.name)
                return True

        return False


    def remove(self, bot):

        """
            delete bot by object.

            :param bot: bot to delete
            :type bot: gozerbot.botbase.BotBase
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                 :pyobject: Fleet.remove

        """

        try:
            self.bots.remove(bot)
            return True
        except ValueError:
            return False

    def exit(self, name=None, jabber=False):

        """
            call exit on all bots. if jabber=True only jabberbots will exit.

            :param name: name of the bot to exit. if not provided all bots will exit.
            :type name: string
            :param jabber: flag to set when only jabberbots should exit
            :type jabber: boolean
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.exit

        """
        
        if not name:

            for i in self.bots:
                if jabber and not i.jabber:
                    pass
                else:
                    i.exit()

            return

        name = name.lower()

        for i in self.bots:

            if i.name == name:
                try:
                    i.exit()
                except:
                    handle_exception()
                self.remove(i)
                return True

        return False

    def cmnd(self, event, name, cmnd):
 
        """
            do command on a bot.

            :param event: event to pass on to the dispatcher
            :type event: gozerbot.event.EventBase
            :param name: name of the bot to pass on to the dispatcher
            :type name: string
            :param cmnd: command to execute on the fleet bot
            :type cmnd: string

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.cmnd

        """

        name = name.lower()
        bot = self.byname(name)

        if not bot:
            return 0

        from gozerbot.eventbase import EventBase
        j = plugins.clonedevent(bot, event)
        j.onlyqueues = True
        j.txt = cmnd
        q = Queue.Queue()
        j.queues = [q]
        j.speed = 3
        plugins.trydispatch(bot, j)
        result = waitforqueue(q)

        if not result:
            return

        res = ["[%s]" % bot.name, ]
        res += result
        event.reply(res)

    def cmndall(self, event, cmnd):

        """
            do a command on all bots.

            :param event: event to pass on to dispatcher
            :type event: gozerbot.eventbase.EventBase
            :param cmnd: the command string to execute
            :type cmnd: string

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.cmndall

        """

        for i in self.bots:
            thread = self.cmnd(event, i.name, cmnd)

    def broadcast(self, txt):

        """
            broadcast txt to all bots.

            :param txt: text to broadcast on all bots
            :type txt: string

            .. literalinclude:: ../../gozerbot/fleet.py
                :pyobject: Fleet.broadcast

        """

        for i in self.bots:
            i.broadcast(txt)

# ============
# INIT SECTION


# main fleet object
fleet = Fleet()

# END INIT
# ========
